#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# DeviceSystem.py
# Copyright (c) 2010 - 2011 Matthew Reid

import os
import socket
import threading
import jsonpickle

from Bases import OSCBaseObject, ChildGroup, Category

from Universe import Universe, InUniverse

from Devices import Device
from Attributes import Attribute

class DeviceSystem(OSCBaseObject):
    _saved_class_name = 'DeviceSystem'
    _saved_child_objects = ['universes', 'input_universes', 'Categories', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices']
    _saved_child_classes = [Universe, Category, Attribute.GlobalAttribute, 
                            Device.DefinedDevice, Device.PatchedDevice]
    def __init__(self, **kwargs):
        self.MainController = kwargs.get('MainController')
        self.comm = self.MainController.comm
        #self.osc_parent_node = self.comm.osc_io.root_node
        #self.osc_address = 'Devices'
        self.Categories = {}
        self.root_category = Category(name='root', id='root')
        for key in ['device', 'attribute', 'effects']:
            category = self.root_category.add_subcategory(name=key, id=key)
            self.Categories.update({key:category.subcategories})
        super(DeviceSystem, self).__init__(**kwargs)
        archive = self.MainController.Archive
        for key in DeviceSystem._saved_child_objects:
            archive.add_member(name=key, 
                               path='DeviceSystem', 
                               filename='%s.js' % (key), 
                               serialize_obj={'DeviceSystem':self}, 
                               serialize_kwargs={'DeviceSystem':{'saved_child_objects':[key]}}, 
                               deserialize_kwargs={'DeviceSystem':{'saved_child_objects':[key]}})
        archive.add_member(name='Patch List', 
                           path='DeviceSystem', 
                           filename='PatchList.csv', 
                           serialize_method='csv', 
                           serialize_obj=self.get_patch_list, 
                           serialize_kwargs={'add_index':False}, 
                           csv_columns=['Index', 'name', 'universe', 'start_chan', 'end_chan', 'definition'])
        self.osc_dump_threads = set()
        self._osc_dumps_pending = {}
        self.osc_child_nodes = {}
        #for name in ['Devices']:
        #    self.osc_child_nodes.update({name:self.osc_node.add_new_node(name=name)})
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump, 
                                        'dump-response':self.on_osc_dump_response, 
                                        'child-added':self.on_osc_child_added})
        self.osc_universe_node = self.comm.osc_io.root_node.add_new_node(name='OSCtoOLA')
        #self.osc_universe_node.addCallback('OSCtoOLA/universes-info', self.on_osc_universes_info)        
        
        #self.DeviceTypes = DeviceTypes().get_types()
        self.universes_by_id = {}
        self.universes = ChildGroup(name='universes', child_class=Universe)
        self.universes.bind(child_added=self._on_universes_child_added, 
                            child_removed=self._on_universes_child_removed)
        self.input_universes = ChildGroup(name='input_universes', child_class=InUniverse)
        
        self.GlobalAttributes = ChildGroup(name='GlobalAttributes', child_class=Attribute.GlobalAttribute)
        self.DefinedDevices = ChildGroup(name='DefinedDevices', child_class=Device.DefinedDevice)
        self.PatchedDevices = ChildGroup(name='PatchedDevices', 
                                         child_class=Device.PatchedDevice, 
                                         osc_parent_node=self.osc_node, 
                                         osc_address='Devices')
        self.DevicesByNum = {}
        self.patch_by_chan = {}
        self.patch_by_attrib = {}
        
        self.category_type_map = {'device':[self.DefinedDevices, self.PatchedDevices], 
                                  'attribute':[self.GlobalAttributes, self.patch_by_attrib]}
    
        self.register_signal('child_added', 'new_category', 'new_universe', 'new_definition', 
                             'new_device', 'new_attribute', 'del_attribute', 'new_input_universe', 
                             'del_device', 'patch_change')
        
        #self.connect('new_universe', self.comm.on_new_universe)
        self.comm.connect('state_changed', self.on_comm_state)
        #self.connect('new_category', self._on_new_category)
        #self.add_universe(name='A', ola_universe_id=1)
        self.signal_dict_map = {'new_category':'Categories', 'new_universe':'universes', 
                                'new_definition':'DefinedDevices', 'new_device':'PatchedDevices'}
        for sig in self.signal_dict_map.iterkeys():
            self.connect(sig, self.on_signals_to_osc)
            
    def get_patch_list(self):
        l = []
        archive = self.MainController.Archive.members.get('DeviceSystem/PatchList.csv')
        if not archive:
            return l
        keys = archive.csv_columns
        for device in self.PatchedDevices.indexed_items.itervalues():
            d = {}
            for key in keys:
                if key == 'universe':
                    univ = self.universes[device.universe_id]
                    d[key] = univ.Index
                elif key == 'definition':
                    d[key] = device.definition.name
                else:
                    d[key] = getattr(device, key)
            l.append(d)
        return l
            
    def on_osc_request_dump(self, **kwargs):
        if self.osc_node.oscMaster:
            values = kwargs.get('values')
            client = kwargs.get('client')
            i = 0
            if len(values):
                for key in values:
                    val = getattr(self, key)
                    if key == 'Categories':
                        s = self.to_json(saved_child_objects=[key], indent=None)
                        self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
                        i += 1
                    else:
                        for ckey, cval in val.iteritems():
                            s = cval.to_json(indent=None)
                            d = jsonpickle.decode(s)
                            s = jsonpickle.encode({'saved_children':{key:{d['attrs']['id']:d}}})
                            self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
                            i += 1
            else:
                s = self.to_json(indent=None)
                self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
            self.osc_node.send_message(address='dump-response', value='dump-complete', client=client)
        
    def on_osc_dump_response(self, **kwargs):
        values = kwargs.get('values')
        if values[0] == 'dump-complete':
            keys = self._osc_dumps_pending.copy().keys()
            keys.sort()
            for key in keys:
                self.from_json(string=self._osc_dumps_pending[key])
                del self._osc_dumps_pending[key]
            #print 'ds dump-complete. keys=', self._osc_dumps_pending.keys()
            return
        #print 'ds adding dump ', values[0]
        self._osc_dumps_pending[values[0]] = values[1]
    
    def on_signals_to_osc(self, **kwargs):
        name = kwargs.get('signal_name')
        id = kwargs.get('id')
        key = self.signal_dict_map.get(name)
        if key and not self.osc_dump_threads:
            obj = getattr(self, key).get(id)
            if obj:
                s = obj.to_json(indent=None)
                self.osc_node.send_message(address='child-added', value=[key, s])
                
    def on_osc_child_added(self, **kwargs):
        values = kwargs.get('values')
        key = values[0]
        d = jsonpickle.decode(values[1])
        s = jsonpickle.encode({'saved_children':{key:{d['attrs']['id']:d}}})
        self.from_json(saved_child_objects=[key], string=s)
        
    def request_osc_updates(self, **kwargs):
        self._osc_dumps_pending.clear()
        objnames = ['universes', 'Categories', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices']
        self.osc_node.send_message(address='request-dump', value=objnames, to_master=True)
        
    def on_comm_state(self, **kwargs):
        if kwargs.get('state'):
            self.osc_universe_node.send_message(root_address='OSCtoOLA-'+socket.gethostname(), 
                                                address='request-universes')
            
    def on_osc_universes_info(self, **kwargs):
        values = kwargs.get('values')
        d = jsonpickle.decode(values[0])
        print 'univ info from osc:', d
    
#    def _on_new_category(self, **kwargs):
#        category = kwargs.get('category')
#        category.connect('obj_update_needed', self._on_category_obj_request)
#        category._check_member_obj()
#    
#    def _on_category_obj_request(self, **kwargs):
#        id = kwargs.get('category_id')
#        obj_id = kwargs.get('obj_id')
#        callback = kwargs.get('callback')
#        for cTypeKey, cTypeVal in self.Categories.iteritems():
#            for catKey, catVal in cTypeVal.iteritems():
#                if catVal.id == id:
#                    category = catVal
#                    category_type = cTypeKey
#        obj = None
#        for obj_dict in self.category_type_map[category_type]:
#            if obj_id in obj_dict:
#                obj = obj_dict[obj_id]
#        callback(obj=obj, id=obj_id)
#    
#    def get_stuff(self):
#        return 'stuff'
    
    def add_universe(self, **kwargs):
        '''
        :Parameters:
            'id' : int, id for the universe, does not auto-assign UUID
            'type': type of universe ('input', 'output'). default is 'output'
            'name' : str
            'from_xml' : xml string which, if specified, the xml will be loaded
        '''
        #id = kwargs.get('id', self.find_next_universe_id())
        name = kwargs.get('name')
        utype = kwargs.get('type', 'output')
        new_kwargs = kwargs.copy()
        if 'type' in new_kwargs:
            del new_kwargs['type']
        keys = ['Artnet_Subnet', 'Artnet_Universe']
        if utype == 'output':
            childgroup = self.universes
            sig = 'new_universe'
        else:
            childgroup = self.input_universes
            sig = 'new_input_universe'
        i = childgroup.find_max_index()
        new_kwargs.setdefault('name', 'Universe %s' % (i + 1))
        if i == 0:
            new_kwargs['Index'] = 1
        
        if len(childgroup):
            last_u = childgroup.indexed_items[childgroup.find_max_index()]
            
            d = dict(zip(keys, [getattr(last_u, key) for key in keys]))
            if d[keys[1]] == 15:
                d[keys[1]] = 0
                d[keys[0]] += 1
            else:
                d[keys[1]] += 1
        else:
            d = dict(zip(keys, [0, 0]))
        new_kwargs.update(d)
        new_kwargs.update({'comm':self.comm, 'osc_address':name})
                           #'osc_parent_node':self.osc_universe_node})
        if utype == 'input':
            new_kwargs['out_universes'] = self.universes
        universe = childgroup.add_child(**new_kwargs)
        #self.universes.update({universe.id:universe})
        #self.patch_by_chan.update({universe.id:{}})
        self.emit(sig, id=universe.id, universe=universe)
        self.emit('child_added')
        return universe
        
    def del_universe(self, **kwargs):
        univ = kwargs.get('universe')
        id = kwargs.get('id')
        if not univ:
            univ = self.universes.get(id)
            if not univ:
                univ = self.input_universes.get(id)
        childgroup = univ.ChildGroup_parent
        if len(self.patch_by_chan.get(univ.id)):
            return False
        univ.unlink()
        if childgroup == self.universes:
            del self.patch_by_chan[univ.id]
            del self.universes_by_id[univ.id]
        childgroup.del_child(univ)
        
    def _on_universes_child_added(self, **kwargs):
        univ = kwargs.get('obj')
        self.universes_by_id[univ.id] = univ
        self.patch_by_chan[univ.id] = {}
        
    def _on_universes_child_removed(self, **kwargs):
        univ = kwargs.get('obj')
        if univ.id in self.universes_by_id:
            del self.universes_by_id[univ.id]
        if univ.id in self.patch_by_chan:
            del self.patch_by_chan[univ.id]
        
    def add_category(self, **kwargs):
        cat_type = kwargs.get('type')
        name = kwargs.get('name')
        if cat_type in self.Categories:
            category = self.root_category.subcategories[cat_type].add_subcategory(**kwargs)
            #self.Categories[cat_type].update({category.id:category})
            self.emit('new_category', type=cat_type, name=category.name, id=category.id, category=category)
            self.emit('child_added')
            print 'new_category:', cat_type, category.name
        
    def add_global_attribute(self, **kwargs):
        '''
        :Parameters:
           'name' : default is empty
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'type' : class specification (in progress)
            'from_xml' : xml string which, if specified, the xml will be loaded
                into the attribute.  default is None. 
        '''
        kwargs.setdefault('root_category', self.root_category.subcategories['attribute'])
        #attribute = Attribute.GlobalAttribute(**kwargs)
        attribute = self.GlobalAttributes.add_child(**kwargs)
        attribute.connect('id_changed', self.on_global_attrib_id_changed)
        #self.GlobalAttributes.update({attribute.id:attribute})
        self.emit('new_attribute', type='GlobalAttribute', id=attribute.id, name=attribute.name)
        self.emit('child_added')
        return attribute
        
    def del_global_attribute(self, **kwargs):
        attribute = kwargs.get('attribute')
        id = kwargs.get('id')
        if not attribute:
            attribute = self.GlobalAttributes.get(id)
        self.GlobalAttributes.del_child(attribute)
        #attribute.unlink()
        #del self.GlobalAttributes[attribute.id]
        self.emit('del_attribute', type='GlobalAttribute', id=attribute.id)
        
    def on_global_attrib_id_changed(self, **kwargs):
        pass
        
    def add_definition(self, **kwargs):
        '''
        :Parameters:
            'name' : default is empty
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'description' : default is empty
            'manufacturer' : default is empty
            'channel_count' : int, maximum number of channels for the device.
                default is 0
            'type' : device type name. default is 'NormalDevice'
            'from_xml' : xml string which, if specified, the xml will be loaded
                into the device.  default is None.
        '''
        
        kwargs.update({'root_category':self.root_category.subcategories['device']})
        kwargs.update({'GlobalAttributes':self.GlobalAttributes})
        #device = Device.DefinedDevice(**kwargs)
        #self.DefinedDevices.update({device.id:device})
        device = self.DefinedDevices.add_child(**kwargs)
        self.emit('new_device', base=device.base_class, type=device.saved_class_name, id=device.id)
        self.emit('child_added')
        return device
        
    def del_definition(self, **kwargs):
        device = kwargs.get('device')
        id = kwargs.get('id')
        if not device:
            device = self.DefinedDevices.get(id)
        self.DefinedDevices.del_child(device)
        #device.unlink()
        #del self.DefinedDevices[device.id]
        self.emit('del_device', type='DefinedDevice', id=device.id)
        
    def patch_device(self, definition, **kwargs):
        '''Takes given device definition and make a patched
        device from it.
        
        :Parameters:
            'definition' : DefinedDevice to use
            'name' : Default is None (which uses definition name)
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'device_num' : int, index of the device to be used in the UI (for organization)
            'universe_id' : id of the DMX universe this device belongs to
            'start_chan' : int, starting DMX channel
            'count' : Number of devices to patch. Default is 1
        '''
        count = int(kwargs.get('count', 1))
        base_start = int(kwargs.get('start_chan', 1))
        start = base_start
        new_kwargs = kwargs.copy()
        new_kwargs.update(dict(definition=definition, DeviceSystem=self))
        #new_kwargs.update({'osc_parent_node':self.osc_child_nodes['Devices']})
        #new_kwargs.update({'root_category':self.root_category.subcategories['device']})
        #new_kwargs.update({'definition':definition})
        for x in range(count):
            #print 'start=', start
            valid = self.test_patch(device=definition, universe_id=kwargs.get('universe_id'), 
                                    start_chan=start)
            if not valid:
                return
            #device_num = self._find_max_device_num() + 1
            new_kwargs.update({'start_chan':start})#, 'device_num':device_num, 'osc_address':device_num})
            device = self.PatchedDevices.add_child(**new_kwargs)
            #device = Device.PatchedDevice(**new_kwargs)
            #device.connect('device_num_update', self.on_device_num_update)
            #self.PatchedDevices.update({device.id:device})
            #self.DevicesByNum.update({device.device_num:device})
            self._update_attributes(device=device)
            self.emit('new_device', base='PatchedDevice', id=device.id, device=device)
            self.emit('child_added')
            start += definition.channel_count
        
    def repatch_device(self, **kwargs):
        '''Repatch an existing device
        :Parameters:
            'device' : 
            'universe_id' : new universe to patch into, or if not given,
                uses the current universe for the device
            'start_chan' : new starting DMX channel
        '''
        device = kwargs.get('device')
        univ_id = kwargs.get('universe_id', device.universe_id)
        start = kwargs.get('start_chan')
        old_univ = device.universe_id
        old_start = device.start_chan
        if not self.test_patch(**kwargs):
            return
        univ = self.universes_by_id[old_univ]
        for attrib in device.Attributes.itervalues():
            lsb = attrib.lsb_attribute
            if lsb:
                univ.del_attribute(lsb)
                #lsb.unbind(univ.on_attribute_value_changed)
                del self.patch_by_chan[old_univ][lsb.chan]
            univ.del_attribute(attrib)
            #attrib.unbind(univ.on_attribute_value_changed)
            del self.patch_by_chan[old_univ][attrib.chan]
        device.repatch(**kwargs)
        self._update_attributes(device=device)
        self.emit('patch_change', device=device, old_universe=old_univ, 
                  new_universe=univ_id, old_start=old_start, new_start=start)
            
    def unpatch_device(self, **kwargs):
        id = kwargs.get('id')
        device = kwargs.get('device')
        if not device:
            device = self.PatchedDevices.get(id)
        if not device or device.id not in self.PatchedDevices:
            return
        device.unpatch()
        for key, val in device.Attributes.iteritems():
            univ = self.universes_by_id[val.universe_id]
            lsb = val.lsb_attribute
            if lsb:
                univ.del_attribute(lsb)
                #lsb.unbind(univ)
                del self.patch_by_chan[val.universe_id][lsb.chan]
            univ.del_attribute(val)
            #val.unbind(univ)
            del self.patch_by_attrib[key]
            del self.patch_by_chan[val.universe_id][val.chan]
        self.PatchedDevices.del_child(device)
        self.emit('del_device', type='PatchedDevice', id=device.id)

    def find_first_start_chan(self, **kwargs):
        '''
        :Parameters:
            'device' :
            'universe_id' :
            'count' : 
        '''
        for start in range(1, 513):
            kwargs['start_chan'] = start
            if self.test_patch(**kwargs):
                return start
        return False
    
    def test_patch(self, **kwargs):
        device = kwargs.get('device')
        univ_id = kwargs.get('universe_id')
        start = kwargs.get('start_chan')
        count = device.channel_count
        if start + count - 1 > 512:
            return False
        flag = True
        for x in range(start, count+start):
            attrib = self.patch_by_chan[univ_id].get(x)
            if attrib:
                if device.saved_class_name == 'DefinedDevice':
                    flag = False
                elif attrib != device.attr_by_chan.get(x):
                    flag = False
        return flag
        
#    def on_device_num_update(self, **kwargs):
#        device = kwargs.get('device')
#        value = kwargs.get('value')
#        old = kwargs.get('old')
#        if old is not None and old != value:
#            del self.DevicesByNum[old]
#            self.DevicesByNum[value] = device
#            print 'changed, old=%s, value=%s' % (old, value)
#        
#    def _find_max_device_num(self):
#        if len(self.DevicesByNum) == 0:
#            return 0
#        return max(self.DevicesByNum.keys())        
        
    def _update_attributes(self, **kwargs):
        kwargs.setdefault('device', None)
        kwargs.setdefault('device_id', None)
        kwargs.setdefault('attribute', None)
        device = kwargs.get('device')
        device_id = kwargs.get('device_id')
        attribute = kwargs.get('attribute')
        if attribute is not None:
            return
        if device_id is not None:
            device = self.PatchedDevices[device_id]
        if device is None:
            return
        for key, val in device.Attributes.iteritems():
            self.patch_by_attrib.update({val.id:val})
            if val.chan is None:
                continue
            self.patch_by_chan[val.universe_id].update({val.chan:val})
            lsb = val.lsb_attribute
            univ = self.universes_by_id[val.universe_id]
            univ.add_attribute(val)
            #univ.on_attribute_value_changed(obj=val, value=val.value)
            #val.bind(value=univ.on_attribute_value_changed)
            if lsb:
                self.patch_by_chan[val.universe_id].update({lsb.chan:lsb})
                univ.add_attribute(lsb)
                #univ.on_attribute_value_changed(obj=lsb, value=lsb.value)
                #lsb.bind(value=univ.on_attribute_value_changed)
                
    def on_new_ola_universe(self, **kwargs):
        univ = kwargs.get('ola_universe')
        for u in self.universes.itervalues():
            if u.ola_universe_id == univ.id:
                univ.Universe = u
        
    def clear_show(self):
        for key in self.PatchedDevices.keys()[:]:
            self.unpatch_device(id=key)
        for key in self.DefinedDevices.keys()[:]:
            self.del_definition(id=key)
        for key in self.GlobalAttributes.keys()[:]:
            self.del_global_attribute(id=key)
        for key in self.universes.keys()[:]:
            self.del_universe(id=key)
        for category in self.root_category.subcategories.itervalues():
            for subid in category.subcategories.keys()[:]:
                category.del_subcategory(id=subid)
        
    def load_globals(self, **kwargs):
        kwargs.setdefault('saved_child_objects', ['Categories', 'GlobalAttributes', 'DefinedDevices'])
        self.from_json(**kwargs)
        
    def save_globals(self, **kwargs):
        return self._get_saved_attr(saved_child_objects=['Categories', 'GlobalAttributes', 'DefinedDevices'])
    
    def get_file_data(self, **kwargs):
        d = {}
        for key in ['Categories', 'GlobalAttributes', 'DefinedDevices', 'ShowData']:
            filename = '.'.join([key, 'js'])
            if key != 'ShowData':
                d[filename] = self._get_saved_attr(saved_child_objects=[key])
            else:
                objs = ['universes', 'input_universes', 'PatchedDevices']
                d[filename] = self._get_saved_attr(saved_child_objects=objs)
        return d
        
    def from_json(self, **kwargs):
        filename = kwargs.get('filename', None)
        string = kwargs.get('string', '')
        if string == '' and filename is not None and os.path.exists(filename):
            file = open(filename, 'r')
            for line in file:
                string += line
            file.close()
        if string != '':
            d = jsonpickle.decode(string)
            self._load_saved_attr(d, **kwargs)
            
    def _load_saved_attr(self, d, **kwargs):
        parse_keys = kwargs.get('saved_child_objects', ['Categories', 'universes', 'input_universes', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices'])
        for pKey in parse_keys:
            if pKey in d['saved_children']:
                for key, val in d['saved_children'][pKey].iteritems():
                    if pKey == 'universes' and key not in self.universes:
                        universe = self.universes.add_child(Universe, deserialize=val, osc_address=val['attrs']['id'], osc_parent_node=self.osc_universe_node, comm=self.comm)
                        #universe.connect('value_update', self.comm.dmx_io['NetlinxDMX'].sendDMXValue)
                        #self.universes.update({universe.id:universe})
                        #self.patch_by_chan.update({universe.id:{}})
                        self.emit('new_universe', id=universe.id, universe=universe)
                        self.emit('child_added')
                    elif pKey == 'input_universes' and key not in self.input_universes:
                        universe = self.input_universes.add_child(deserialize=val, 
                                                                  osc_address=val['attrs']['id'], 
                                                                  out_universes=self.universes, 
                                                                  comm=self.comm)
                        self.emit('new_input_universe', id=universe.id, universe=universe)
                        self.emit('child_added')
                    elif pKey == 'Categories':
                        for cKey, cVal in val.iteritems():
                            #category = Category(deserialize=cVal)
                            parent = self.root_category.find_category(id=key)
                            if not parent:
                                parent = self.root_category.add_subcategory(deserialize=cVal)
                                self.Categories.update({key:parent.subcategories})
                            category = self.root_category.find_category(id=cKey)
                            if not category:
                                category = parent.add_subcategory(deserialize=cVal)
                                #self.Categories[key].update({category.id:category})
                                self.emit('new_category', type=key, name=category.name, id=category.id, category=category)
                                self.emit('child_added')
                    elif pKey == 'GlobalAttributes' and key not in self.GlobalAttributes:
                        if key not in self.GlobalAttributes:
                            a_kwargs = dict(deserialize=val, root_category=self.root_category.subcategories['attribute'])
                            #attribute = Attribute.GlobalAttribute(**a_kwargs)
                            #self.GlobalAttributes.update({key:attribute})
                            attribute = self.GlobalAttributes.add_child(**a_kwargs)
                            self.emit('new_attribute', type='GlobalAttribute', id=key, name=attribute.name)
                            self.emit('child_added')
                    elif pKey == 'DefinedDevices' and key not in self.DefinedDevices:
                        if key not in self.DefinedDevices:
                            d_kwargs = dict(deserialize=val, root_category=self.root_category.subcategories['device'])
                            d_kwargs.update({'GlobalAttributes':self.GlobalAttributes})
                            #device = Device.DefinedDevice(**d_kwargs)
                            #self.DefinedDevices.update({key:device})
                            device = self.DefinedDevices.add_child(**d_kwargs)
                            self.emit('new_device', base='DefinedDevice', type=device.saved_class_name, 
                                      id=key, name=device.name)
                            self.emit('child_added')
                    elif pKey == 'PatchedDevices' and key not in self.PatchedDevices:
                        if key not in self.PatchedDevices:
                            definition = self.DefinedDevices[val['attrs']['definition_id']]
                            d_kwargs = dict(osc_address=val['attrs'].get('osc_address'), 
                                            deserialize=val, DeviceSystem=self, definition=definition)
                            #d_kwargs.update({'deserialize':val, 'root_category':self.root_category.subcategories['device'], 
                            #                 'DeviceSystem':self, 'definition':definition}
##weird by joshua  
## sam flynn
                            #d_kwargs.update({'definition':definition})
                            device = self.PatchedDevices.add_child(**d_kwargs)
                            #device.connect('device_num_update', self.on_device_num_update)
                            #self.PatchedDevices.update({key:device})
                            #self.DevicesByNum.update({device.device_num:device})
                            self._update_attributes(device=device)
                            self.emit('new_device', base='PatchedDevice', type=device.saved_class_name, 
                                      id=key, name=device.name, device=device)
                            self.emit('child_added')
